/**
 * Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
 *
 * @licstart
 * This file is part of WebODF.
 *
 * WebODF is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License (GNU AGPL)
 * as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * WebODF is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with WebODF.  If not, see <http://www.gnu.org/licenses/>.
 * @licend
 *
 * @source: http://www.webodf.org/
 * @source: https://github.com/kogmbh/WebODF/
 */

/*global odf, runtime, core, Node*/

/**
 * Helper object for generating unique object names. Each name is only reported once per instance,
 * irrespective of whether it is actually then inserted into the dom tree in the odfContainer.
 *
 * There is expected to be a single instance of the object name generator created per session. This is necessary
 * to close a potential race condition when generating unique names for operations. As there is no guarantee
 * when a given op is executed, it is insufficient to simply rely on all previously generated names to be now present
 * in the document definitions. To cope with this, the names generated by this instance are also cached for
 * the lifetime of this object.
 *
 * Failure to do this could result in a situation like the following
 * 1. SessionController generates new OpAddStyle & adds to session's queue
 * 2. SessionController generates another OpAddStyle & adds to session's queue
 *
 * At step 2, as the session's queue implementation has no requirement that it immediately executes the operation from
 * step 1, it is likely that the style created in step 1 is not yet present in the document DOM.
 *
 * @param {!odf.OdfContainer} odfContainer
 * @param {!string} memberId
 * @constructor
 */
odf.ObjectNameGenerator = function ObjectNameGenerator(odfContainer, memberId) {
    "use strict";
    var stylens = odf.Namespaces.stylens,
        drawns = odf.Namespaces.drawns,
        xlinkns = odf.Namespaces.xlinkns,
        utils = new core.Utils(),
        memberIdHash = utils.hashString(memberId),
        styleNameGenerator = null,
        frameNameGenerator = null,
        imageNameGenerator = null,
        existingFrameNames = {},
        existingImageNames = {};

    /**
     * @param {string} prefix Prefix to use for unique name generation
     * @param {function():!Object.<string,boolean>} findExistingNames
     * @constructor
     */
    function NameGenerator(prefix, findExistingNames) {
        var /**@type{!Object.<string,boolean>}*/
            reportedNames = {};
        /**
         * Generate a unique name
         * @return {string}
         */
        this.generateName = function () {
            var existingNames = findExistingNames(),
                startIndex = 0,
                name;
            do {
                name = prefix + startIndex;
                startIndex += 1;
            } while (reportedNames[name] || existingNames[name]);
            reportedNames[name] = true;
            return name;
        };
    }

    /**
     * Get all the style names defined in the style:style elements of the
     * current document including automatic styles.
     *
     * @return {!Object.<string,boolean>}
     */
    function getAllStyleNames() {
        var styleElements = [
                odfContainer.rootElement.automaticStyles,
                odfContainer.rootElement.styles
            ],
            styleNames = {};

        /**
         * @param {!Element} styleListElement
         */
        function getStyleNames(styleListElement) {
            var e = styleListElement.firstElementChild;
            while (e) {
                if (e.namespaceURI === stylens && e.localName === "style") {
                    styleNames[e.getAttributeNS(stylens, 'name')] = true;
                }
                e = e.nextElementSibling;
            }
        }
        styleElements.forEach(getStyleNames);
        return styleNames;
    }

    /**
     * Generate a unique style name across the style:style elements
     * @return {!string}
     */
    this.generateStyleName = function () {
        if (styleNameGenerator === null) {
            styleNameGenerator = new NameGenerator(
                "auto" + memberIdHash + "_",
                function () {
                    // TODO: can cache the existing names once we fix the todo in formatting.applyStyle
                    return getAllStyleNames();
                }
            );
        }
        return styleNameGenerator.generateName();
    };
    /**
     * Generate a unique frame name
     * @return {!string}
     */
    this.generateFrameName = function () {
        var i, nodes, node;
        if (frameNameGenerator === null) {
            nodes = odfContainer.rootElement.body.getElementsByTagNameNS(drawns, 'frame');
            for (i = 0; i < nodes.length; i += 1) {
                node = /**@type{!Element}*/(nodes.item(i));
                existingFrameNames[node.getAttributeNS(drawns, 'name')] = true;
            }

            frameNameGenerator = new NameGenerator(
                "fr" + memberIdHash + "_",
                function () {
                    return existingFrameNames;
                }
            );
        }
        return frameNameGenerator.generateName();
    };
    /**
     * Generate a unique image name
     * @return {!string}
     */
    this.generateImageName = function () {
        var i, path, nodes, node;

        if (imageNameGenerator === null) {
            nodes = odfContainer.rootElement.body.getElementsByTagNameNS(drawns, 'image');
            for (i = 0; i < nodes.length; i += 1) {
                node = /**@type{!Element}*/(nodes.item(i));
                path = node.getAttributeNS(xlinkns, 'href');
                path = path.substring("Pictures/".length, path.lastIndexOf('.'));
                existingImageNames[path] = true;
            }

            imageNameGenerator = new NameGenerator(
                "img" + memberIdHash + "_",
                function () {
                    return existingImageNames;
                }
            );
        }
        return imageNameGenerator.generateName();
    };
};
